package edu.mercer.sse.configuration;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.google.common.base.Optional;

import static com.google.common.base.Preconditions.*;
import edu.mercer.sse.annotations.Fulfills;
import edu.mercer.sse.error.CriticalException;

/**
 * Application Configuration.<p>
 *
 * Java Properties stored in XML file. Default: config.xml<p>
 * 
 * Factory accepts full path as argument.<p>
 * 
 * Examples:
 * <ul> 
 * <li> /myfolder/plainpdf/myconfigfile.xml
 * <li> /home/doe_j/plainpdf/config.xml
 * </ul>
 *
 * @see AppProperties
 *
 * @see Configuration
 */

public class Config implements Configuration<AppProperties<String,String>>
{
	/** Application Name */
	
    private final static String APP_NAME = "PlainPDF";

        
	/** Optional full path: default is current folder  */
    
    private String _path = "";

    
	/** XML Properties filename: default is config.xml */
    
    private String _configFile = "config.xml";

    
	/** Properties */
    
    private Optional<Properties> _properties = Optional.absent();

    
    /** 
     * ConcurrentMap&#60;String,Configuration&#62; for this.<p>
     * 
     * Singleton for this. Factory only creates one global instance. APP_NAME 
     * 	used as key.<p>
     */
    
    private static final ConcurrentMap<String,
    								   Configuration<AppProperties
    								   <String,String>>> _singletonThis =
    								   
    		new ConcurrentHashMap<String,
    							  Configuration<AppProperties
    							  <String,String>>>();

 
    /*
     * Instantiation: Constructor, Factory
     */

	/** 
	 * Constructor.<p>
	 * 
	 * Override default path, filename for XML Properties file.<p>
	 * 
	 * @param xmlFilename - String containing XML filename and any path.
	 */
    
    private Config( final String xmlFilename )
    {
    	checkConfigFile( xmlFilename );
    	
    	setConfigFile( xmlFilename );
    	
    	init();
            
    }//constructor


    /**
     * Constructor.<p>
     * 
     * Default path, filename of XML Properties used.<p>
     */
    
    private Config()
    {
    	checkConfigFile( _path + _configFile );
    	
    	init();
            
    }//constructor
    

	/** 
	 * Factory.<p>
	 * 
	 * @param propertiesFilePath - String full path, file name of XML 
	 * 	Properties
	 * 
	 * @see #factory()
	 * 
	 */
    
    public static Configuration<AppProperties<String,String>> factory( 
    		final String propertiesFilePath )
    {
    	_singletonThis.putIfAbsent( getAppName(), 
    								new Config( propertiesFilePath ) );

    	return _singletonThis.get( getAppName() );
    	
    }//method
    
    
	/** 
	 * Factory.<p>
	 * 
	 * Use default: 'config.xml' for Properties file<p>
	 * 
	 * @see #factory(String)
	 */
    
    public static Configuration<AppProperties<String,String>> factory()
    {
    	_singletonThis.putIfAbsent( getAppName(), 
    								new Config() );

    	return _singletonThis.get( getAppName() );
    	
    }//method


    /*
     * Initialization
     */
    
	/**
	 * Ensure XML file accessible, non-empty.<p> 
	 * 
	 * @throws IllegalArgumentException - XML file inaccessible or empty
	 */
    
    private void checkConfigFile( final String xmlFilePath )
    {
    	checkNotNull( xmlFilePath, "xml filename: null" );

    	checkArgument( new File( xmlFilePath ).canRead(), 
    			"xml file not readable: " + xmlFilePath );

    	checkArgument( new File( xmlFilePath ).length() > 0, 
    			"xml file is empty: " + xmlFilePath );
    	
    }//method
        
    
	/**
	 * Clear, reload Properties collection.<p> 
	 */
    
    private Properties init()
    {
		if( _properties.isPresent() )
		{
			_properties.get().clear();
		
		}
		else
		{
			_properties = Optional.of( new Properties() );
			
		}//if
		
		return readProperiesXML();
    	
    }//method

    
	/** 
	 * Read and parse XML Properties file into Properties collection.<p>
	 * 
	 * @return  Properties - properties as parsed from XML
	 */
    
    private Properties readProperiesXML()
    {
    	try( 
    			final FileInputStream inFile = new FileInputStream( 
    					getConfigFile() );    			
    	   )
    	{
    		if( ! _properties.isPresent() ) _properties = 
    				
    				Optional.of(new Properties()); 
    		
    		_properties.get().loadFromXML(inFile);
            inFile.close();    		
    		    		
    	}
    	catch( final IOException err )
    	{
    		/*
    		 * do not use logging visitor here, as it depends on Configuration
    		 */
    		
    		throw new CriticalException(err);
    		
    	}//try
    	
    	return _properties.get();
    	
    }//method
    
    
    /*
     * Getters, Setters
     */

    /**
     * Get XML Properties filename.<p>
     * 
     * @return  String - XML Properties filename
     */
    
    private String getConfigFileName(){ return _configFile; }

    
    /**
     * Get XML Properties file path.<p>
     * 
     * @return  String - XML Properties file path
     */
    
    private String getConfigFilePath(){ return _path; }

    
    /**
     * Get XML Properties file path + file name.<p>
     * 
     * @return  String - XML Properties file path concatenated with file name
     */
    
    private String getConfigFile()
    { 
    	String fileXML = getConfigFilePath() + getConfigFileName();
    	
    	checkConfigFile( fileXML );
    	
    	return fileXML; 
    	
    }//method
    
    
    /**
     * Set XML Properties path, if any, concatenated with filename.<p>
     * 
     * @param configFile - String XML Properties filename prepended with 
     * 	any required path
     */
    
    private Config setConfigFile( final String configFile )
    { 
    	_configFile = configFile;
    	
    	return this;
    	
    }//method

    
    /**
     * Get application name.<p>
     * 
     * Used by object cache, singleton factory methods.<p>
     * 
     * @return  String containing application name; i.e., 'PlainPDF'
     */
    
    public static String getAppName(){ return APP_NAME; }


    /*
     * Interface methods
     */
    
    @Fulfills("Configuration")
	public AppProperties<String,String> load() 
	{		
    	return AppProp.factory( _properties.get() );
	
	}//method

    
    @Fulfills("Configuration")
	public AppProperties<String,String> refresh() 
	{
		init();
		
    	return AppProp.factory( _properties.get() );
		
	}//method


}//class
